package p46_value注入原理;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.stereotype.Component;

/**
 * 这节介绍bean后处理器，是如何解析@Value的(第四讲的延续)
 * ContextAnnotationAutowireCandidateResolver这个bean后处理器就进行解析
 * 解析步骤:
 *  1.拿到注解的值
 *  2.尝试按照${}解析
 *  3.尝试按照#{}解析
 *  4.做必要的类型转换
 */
@Configuration
@Slf4j
public class _Value {
    public static void main(String[] args) throws NoSuchFieldException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(_Value.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        //这个类可以解析@Value，并根据表达式从容器中得到值
        ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
        resolver.setBeanFactory(beanFactory);

        /**
         * 假设bean后处理器已经收集到家了@Value注解的属性
         * 之后会将该属性封装成DependencyDescriptor
         * resolver.getSuggestedValue(dd1)会得到对应属性的@Value注解的值
         * 解析${},就从容器的环境对象中拿到值,context.getEnvironment().resolvePlaceholders(home);
         * 解析#{},通过BeanExpressionResolver组件解析，
         *    context.getBeanFactory().getBeanExpressionResolver().evaluate(father, new BeanExpressionContext(beanFactory, null));
         * 如果解析的值与属性类型不匹配，则调用类型转换器进行转换，dd.getDependencyType()可以得到属性原本类型
         * 最终得到一个值，通过反射将该值赋给对应的属性或者方法参数
         */
        //解析加了@Value注解的属性
        DependencyDescriptor dd1 = new DependencyDescriptor(Bean1.class.getDeclaredField("home"), false);
        //得到@Value直接的value值
        String home = resolver.getSuggestedValue(dd1).toString();
        //根据value值去容器中找
        log.info(context.getEnvironment().resolvePlaceholders(home));

        //解析age属性,字面量+类型转换.在解析${}的基础上加了类型转换
        DependencyDescriptor dd2 = new DependencyDescriptor(Bean1.class.getDeclaredField("age"), false);
        String age = resolver.getSuggestedValue(dd2).toString();
        age = context.getEnvironment().resolvePlaceholders(age);
        Object o = beanFactory.getTypeConverter().convertIfNecessary(age, dd2.getDependencyType());
        System.out.println(o.getClass());

        //解析father
        DependencyDescriptor dd3 = new DependencyDescriptor(Bean1.class.getDeclaredField("father"), false);
        String father = resolver.getSuggestedValue(dd3).toString();
        father = context.getEnvironment().resolvePlaceholders(father);
        //尝试解析#{}
        BeanExpressionResolver el = context.getBeanFactory().getBeanExpressionResolver();
        Object f = el.evaluate(father, new BeanExpressionContext(beanFactory, null));
        f = beanFactory.getTypeConverter().convertIfNecessary(f, dd3.getDependencyType());
        System.out.println(f.getClass().getName());

        /**
         * 上面演示了@Value内部的解析api，实际的解析步骤
         *  1.先拿到@Value注解的value值,也就是调用ContextAnnotationAutowireCandidateResolver.getSuggestedValue(DependencyDescriptor)
         *  2.用拿到的值，进行${}解析，也就是调用ConfigurableEnvironment.resolvePlaceholders(home)
         *  3.在进行#{}解析，也就是调用BeanExpressionResolver.evaluate(String, BeanExpressionContext)
         *  4.在尝试类型转换，也就是TypeConverter.convertIfNecessary("解析出来的值", "目标类型");
         * 下面演示了一个综合案例,@Value("${JAVA_HOME} hello #{@_Value.toString()}")
         */
        DependencyDescriptor dd = new DependencyDescriptor(Bean1.class.getDeclaredField("hello"), false);
        //获取@Value注解的内容，"${JAVA_HOME} hello #{@_Value.toString()}"
        String value = resolver.getSuggestedValue(dd).toString();
        System.out.println(value);
        //按照${}解析, "D:\env\jdk1.8 hello #{@_Value.toString()}"
        value = context.getEnvironment().resolvePlaceholders(value);
        System.out.println(value);
        //按照#{}解析,"D:\env\jdk1.8 hello 这个value"
        Object evaluate = el.evaluate(value, new BeanExpressionContext(beanFactory, null));
        System.out.println(evaluate);
        //尝试类型转换，"D:\env\jdk1.8 hello 这个value"
        evaluate = beanFactory.getTypeConverter().convertIfNecessary(evaluate, dd.getDependencyType());
        System.out.println(evaluate);
    }

    @Data
    @Component
    static class Bean1 {
        //配合${}，可以从环境变量找到值
        @Value("${JAVA_HOME}")
        private String home;
        //直接注入字面量(存在类型转换)
        @Value("18")
        private int age;
        //#{}，SpEL表达式，@表示根据名字找一个bean赋值给属性
        @Value("#{@_Value}")
        private _Value father;
        //综合案例
        @Value("${JAVA_HOME} hello #{@_Value.toString()}")
        private String hello;
    }

    @Override
    public String toString() {
        return "这个value";
    }
}
